package mviewer.renderer.software.shader;

import mviewer.renderer.software.interfaces.ILighting;
import java.awt.Graphics;
import java.awt.Polygon;
import java.awt.Color;
import mviewer.renderer.software.interfaces.IBuffer;
import mviewer.renderer.software.interfaces.IShader;
import mviewer.types.IPolygon;
import mviewer.types.IVector;
import mviewer.types.IVertex;

public class GouraudShader implements IShader {

	static private class Vector2d {

		public int x, y;

		public Vector2d(int x, int y) {
			this.x = x;
			this.y = y;
		}
	}

	private Color[] clr;
	private Polygon p2;
	private IPolygon p3;
	private IVertex[] v;
	private Vector2d fourth;
	private int middle, top, bottom, left, right, leftX;
	private float dxl, dxr, dzx, dzy, w;
	private IBuffer buffer;

	@Override
	public void drawPolygon(Graphics g, IPolygon p3, Polygon p2,
			ILighting light, IBuffer buffer) {
		// get point colors
		v = p3.getVertices();
		clr = new Color[] {light.renderColor(v[0], v[0].getNormal(), p3.getMaterial()),
						   light.renderColor(v[1], v[1].getNormal(), p3.getMaterial()),
						   light.renderColor(v[2], v[2].getNormal(), p3.getMaterial()),
						   null};
		this.p2 = p2;
		this.p3 = p3;
		this.buffer = buffer;
		g.setColor(light.renderColor(v[0], p3.getNormal(), p3.getMaterial()));

		dectectIndexAttitudes();
		if (p2.ypoints[top]==p2.ypoints[middle]) {
			detectScanlineIncrease(bottom, top, 1);
			fourth = new Vector2d(p2.xpoints[top], p2.ypoints[top]);
			clr[3] = clr[top];
			if (fourth.x<p2.xpoints[middle]) {
				left = 3;
				right = middle;
				leftX = fourth.x;
			} else {
				left = middle;
				right = 3;
				leftX = p2.xpoints[middle];
			}
			w = Math.abs(fourth.x-p2.xpoints[middle]);
			detectZIncrease(1);
			drawHalf(g, bottom, 1);
		} else if (p2.ypoints[bottom]==p2.ypoints[middle]) {
			detectScanlineIncrease(top, bottom, -1);
			fourth = new Vector2d(p2.xpoints[bottom], p2.ypoints[bottom]);
			clr[3] = clr[bottom];
			if (fourth.x<p2.xpoints[middle]) {
				left = 3;
				right = middle;
				leftX = fourth.x;
			} else {
				left = middle;
				right = 3;
				leftX = p2.xpoints[middle];
			}
			w = Math.abs(fourth.x-p2.xpoints[middle]);
			detectZIncrease(1);
			drawHalf(g, top, -1);
		} else {
			detectScanlineIncrease(bottom, top, 1);
			float tmp;
			int halfHeight = p2.ypoints[middle]-p2.ypoints[bottom];
			fourth = new Vector2d((int)((tmp=p2.xpoints[bottom] + dxr*halfHeight)==p2.xpoints[middle]
						? p2.xpoints[bottom] + dxl*halfHeight
						: tmp)
				, p2.ypoints[middle]);
			if (fourth.x<p2.xpoints[middle]) {
				left = 3;
				right = middle;
				leftX = fourth.x;
			} else {
				left = middle;
				right = 3;
				leftX = p2.xpoints[middle];
			}
			float cFactor = halfHeight / (float)(p2.ypoints[top]-p2.ypoints[bottom]);
			clr[3] = new Color((int)((((1-cFactor)*clr[bottom].getRed()) + cFactor*clr[top].getRed()) / 2)
						, (int)((((1-cFactor)*clr[bottom].getGreen()) + cFactor*clr[top].getGreen()) / 2)
						, (int)((((1-cFactor)*clr[bottom].getBlue()) + cFactor*clr[top].getBlue()) / 2));
			w = Math.abs(fourth.x-p2.xpoints[middle]);
			detectZIncrease(1);
			drawHalf(g, bottom, 1);
			detectScanlineIncrease(top, bottom, -1);
			detectZIncrease(1);
			drawHalf(g, top, -1);
		/*	g.setColor(clr[3]);
			g.drawLine(fourth.x, fourth.y, fourth.x+5, fourth.y);*/
		}
	}

	private void drawHalf(Graphics g, int start, int sign) {
		float xStart = p2.xpoints[start], xStop = p2.xpoints[start], z,
			height = Math.abs(p2.ypoints[middle]-p2.ypoints[start]), h, tmp,
			dx = v[middle].getX() - v[start].getX(),
			dy = v[middle].getY() - v[start].getY(),
			dz = v[middle].getZ() - v[start].getZ();

/*		if (dz == 0) {
			dzx = dzy = 0;
			dzxM = dzyM = 1;
		} else {
			dzx = dzxM = dx / dz;
			dzy = dzyM = dy / dz;
		}*/

		for (int y = p2.ypoints[start]; sign > 0 ? y < p2.ypoints[middle] : y >= p2.ypoints[middle]; y+=sign) {
			z = v[start].getZ()
					+ dzx * (xStart - p2.xpoints[start])
					+ dzy * (y - p2.ypoints[start]);
			h = (y - p2.ypoints[start]) / height;
			if (sign < 0)
					h = 1 - h;
			for (int x = (int) xStart; x <= xStop; x++) {
				z += dzx;
				//g.setColor(getColor(start, h, (x-leftX)/w));
				if (x >= 0 && x < buffer.getWidth() &&
						y >= 0 && y < buffer.getHeight() &&
						buffer.displayPixel(x, y, z))
					g.drawLine(x, y, x, y);
			}
			xStart += dxl;
			xStop += dxr;
		}
	}

	private Color getColor(int start, float h, float v) {
		if (h<0) h = 0;
		if (h>1) h = 1;
		if (v<0) v = 0;
		if (v>1) v = 1;
		return new Color((int)(((1-h)*v*clr[start].getRed() + h*(1-v)*clr[left].getRed() + h*v*clr[right].getRed()) / 3)
				, (int)(((1-h)*v*clr[start].getGreen() + h*(1-v)*clr[left].getGreen() + h*v*clr[right].getGreen()) / 3)
				, (int)(((1-h)*v*clr[start].getBlue() + h*(1-v)*clr[left].getBlue() + h*v*clr[right].getBlue()) / 3));
	}

	private void detectZIncrease(int sign) {
		IVector n = p3.getNormal();
		dzx = n.getX()==0 || n.getZ()==0 ? 0 : n.getX() / n.getZ();
		dzy = n.getY()==0 || n.getZ()==0 ? 0 : n.getY() / n.getZ() * sign;
	}

	private void detectScanlineIncrease(int t, int b, int factor) {
		float tmp1, tmp2;
		dxl = ((tmp1=p2.xpoints[t]-p2.xpoints[middle])==0 || (tmp2=p2.ypoints[t]-p2.ypoints[middle])==0 ? 0 : tmp1 / tmp2 * factor);
		dxr = ((tmp1=p2.xpoints[t]-p2.xpoints[b])==0 || (tmp2=p2.ypoints[t]-p2.ypoints[b])==0 ? 0 : tmp1 / tmp2 * factor);
		if (dxl>dxr) {
			tmp1 = dxl;
			dxl = dxr;
			dxr = tmp1;
		}
	}

	private void dectectIndexAttitudes() {
		// get indices by positions
		if (p2.ypoints[0]<p2.ypoints[1])
			if (p2.ypoints[0]>p2.ypoints[2]) {
				middle = 0;
				top = 1;
				bottom = 2;
			} else {
				bottom = 0;
				if (p2.ypoints[2]>p2.ypoints[1]) {
					top = 2;
					middle = 1;
				} else {
					top = 1;
					middle = 2;
				}
			}
		else
			if (p2.ypoints[1]>p2.ypoints[2]) {
				middle = 1;
				top = 0;
				bottom = 2;
			} else {
				bottom = 1;
				if (p2.ypoints[2]>p2.ypoints[0]) {
					top = 2;
					middle = 0;
				} else {
					top = 0;
					middle = 2;
				}
			}
	}
}
